﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;
using System.IO;

namespace Cqrs.Infrastructure.Aggregate
{
    public class StateTransitions
    {
        private Dictionary<String, State> _states = new Dictionary<String, State>();

        public StateTransitions(string xmlFile, Assembly eventsAssembly)
        {
            SetupStateTransitionsFromXml(xmlFile, eventsAssembly);
        }

        private void SetupStateTransitionsFromXml(string scxml, Assembly eventsAssembly)
        {
            string initialState = "";
            XElement root = XElement.Parse(scxml);
            if (root.Attributes("initial").Count() > 0)
            {
                initialState = root.Attributes("initial").First().Value;
            }

            foreach (var stateNode in root.Elements())
            {
                if (stateNode.Name.LocalName != "State" && stateNode.Name.LocalName != "Final")
                    break;

                string stateName = stateNode.Attributes("id").First().Value;
                State state = new State(stateName);
                if (string.Equals(stateName, initialState, StringComparison.OrdinalIgnoreCase))
                {
                    InitialState = state;
                }

                AddState(state);
            }

            foreach (var stateNode in root.Elements())
            {
                foreach (var transition in stateNode.Elements())
                {
                    AddTransitionToState(eventsAssembly, stateNode.Attributes("id").First().Value, transition);
                }
            }
        }

        private void AddTransitionToState(Assembly eventsAssembly, string stateName, XElement transition)
        {

            if (transition.Attributes("event").Count() == 0)
                throw new Exception("The scxml file contains a transition with no event");

            string eventName = transition.Attributes("event").FirstOrDefault().Value;           

            var eventsWithState = eventsAssembly.GetExportedTypes().Where(p => p.IsSubclassOf(typeof(EventWithState))).ToList();
            var eventType = eventsWithState.First(p => p.Name == eventName);

            if (eventType == null)
                throw new ApplicationException(
                    string.Format("The Event {0} was not found in the {1} assembly.",
                    eventName,
                    eventsAssembly.GetName(true).Name));

            States[stateName]
                .AddTransition(eventType,
                                    States[transition.Attribute("target").Value]);
        }

        public Dictionary<String, State> States
        {
            get
            {
                return _states;
            }
        }

        public void AddState(State state)
        {
            _states.Add(state.Name, state);
        }

        public State InitialState { get; private set; }

    }
}
